/**
 * http://khayyam.developpez.com/articles/algo/perlin/#LV-3
 */

package carte;

import java.util.Random;

public class PerlinNoise {

	private static final float DEFAULT_PERSISTENCE = 0.3f;
	private static final int DEFAULT_NBOCTAVES = 7;
	private static final int DEFAULT_FREQUENCY = 4;

	// l'altitude maximum de monde cree
	private int maxAlt;
	// la persistence d�fini le niveau de rugosit� de la map plus elle est
	// �l�ver plus le monde est rugeux, elle est comprise entre 0 et 1
	private float persistence;
	// le nombre nombre d'octaves, plus ce nombre est �lev� plus le terrain est
	// r�aliste (plus le temps de cr�ation est long)
	private int numberOfOctaves;

	// valeurs aleatoires initiales
	private float[][] base;
	// taille de la nouvelle map
	private int sizeMap;

	private Random rand;

	public PerlinNoise(int tailleCarte, int maxAlt) {
		rand = new Random();
		sizeMap = tailleCarte;
		this.maxAlt = maxAlt;
		persistence = DEFAULT_PERSISTENCE;
		numberOfOctaves = DEFAULT_NBOCTAVES;
		// on initialise la base
		makeRandombase();
	}

	private void makeRandombase() {
		base = new float[sizeMap][sizeMap];
		for (int i = 0; i < sizeMap; i++) {
			for (int j = 0; j < sizeMap; j++) {
				// entre 0 et maxAlt exclu
				base[i][j] = rand.nextInt(maxAlt);
			}
		}
	}

	private float interpolate(float y1, float y2, int interval, int delta) {
		if (interval == 0)
			return y1;
		if (interval == 1)
			return y2;

		float a = (float) delta / interval;

		float v1 = (float) (3 * Math.pow(1 - a, 2) - 2 * Math.pow(1 - a, 3));
		float v2 = (float) (3 * Math.pow(a, 2) - 2 * Math.pow(a, 3));

		return y1 * v1 + y2 * v2;
	}

	private float InterpolatedNoise(int x, int y, int frequency) {
		int size = sizeMap;
		int lowerBoundX, lowerBoundY, upperBoundX, upperBoundY, quotient;
		float step = (float) sizeMap / frequency;
		quotient = (int) ((float) x / step);
		lowerBoundX = (int) (quotient * step);
		upperBoundX = (int) ((quotient + 1) * step);
		if (upperBoundX >= size) {
			upperBoundX = size - 1;
		}
		quotient = (int) ((float) y / step);
		lowerBoundY = (int) (quotient * step);
		upperBoundY = (int) ((quotient + 1) * step);
		if (upperBoundY >= size) {
			upperBoundY = size - 1;
		}
		float s00, s01, s10, s11;
		s00 = base[lowerBoundX][lowerBoundY];
		s01 = base[lowerBoundX][upperBoundY];
		s10 = base[upperBoundX][lowerBoundY];
		s11 = base[upperBoundX][upperBoundY];
		float v1 = interpolate(s00, s01, upperBoundY - lowerBoundY, y
				- lowerBoundY);
		float v2 = interpolate(s10, s11, upperBoundY - lowerBoundY, y
				- lowerBoundY);
		return interpolate(v1, v2, upperBoundX - lowerBoundX, x - lowerBoundX);
	}

	public int PerlinNoise(int x, int y) {
		float noiseSum = 0;
		int frequency = DEFAULT_FREQUENCY;
		float amplitude = persistence;
		float sumAmplitude = 0;
		for (int i = 0; i < numberOfOctaves; i++) {
			noiseSum += InterpolatedNoise(x, y, frequency) * amplitude;
			frequency <<= 1;
			sumAmplitude += amplitude;
			amplitude *= persistence;
		}

		return (int) (noiseSum / sumAmplitude);
	}
}
